// -----------------------------------------------------------------------------
// This source file is part of Matrix Platform
// 	(Universal .NET Software Development Platform)
// For the latest info, see http://www.matrixplatform.com
// 
// Copyright (c) 2009-2010, Ingenious Ltd
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// -----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;

#if Matrix_Diagnostics
using Matrix.Common.Diagnostics;
#endif

namespace Matrix.Framework.DataStorage.DataSeries
{
    /// <summary>
    /// 
    /// </summary>
    public class DataSeriesManipulator : IDisposable
    {
        /// <summary>
        /// 
        /// </summary>
        static protected int ItemHeaderSize
        {
            get
            {
                return 2 * sizeof(uint);
            }
        }

        public DataSeriesSequenceHelper SequenceHelper { get; protected set; }

        protected FileStream _fileStream;
        
        /// <summary>
        /// 
        /// </summary>
        public long FileStreamSize
        {
            get
            {
                FileStream fileStream = _fileStream;
                if (fileStream != null)
                {
                    return fileStream.Length;
                }

                return 0;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public DataSeriesManipulator()
        {
        }

        protected void Initialize()
        {
            SequenceHelper = new DataSeriesSequenceHelper(DataSeriesSequenceHelper.DefaultEscapeSequence);
        }

        /// <summary>
        /// 
        /// </summary>
        public virtual void Dispose()
        {
            DataSeriesSequenceHelper sequenceHelper = SequenceHelper;
            if (sequenceHelper != null)
            {
                SequenceHelper = null;
                sequenceHelper.Dispose();
            }

            FileStream fileStream = _fileStream;
            if (fileStream != null)
            {
                _fileStream = null;
                fileStream.Dispose();
            }
        }

        /// <summary>
        /// Write item to writer.
        /// </summary>
        public static bool CreateItemToWriter(IItemSerializer serializer, BinaryWriter writer,
                                              DataSeriesSequenceHelper sequenceHelper, MemoryStream stream, uint itemIndex, object item)
        {
            // Reset.
            writer.Seek(0, SeekOrigin.Begin);
            stream.SetLength(0);

            // Write the index first.
            writer.Write(itemIndex);

            // Default indication of no escape sequences in user data.
            writer.Write(false);

            // Placeholder for the real length value.
            writer.Write(uint.MaxValue);

            long itemStart = stream.Length;
            if (serializer.WriteItem(writer, itemIndex, item) == false)
            {// Failed to write item.
                return false;
            }

            long itemDataSize = stream.Length - itemStart;

            // Write the actual size.
            writer.Seek(sizeof(uint) + 1, SeekOrigin.Begin);
            writer.Write((int)stream.Length);

            List<long> dataEscapePositions = sequenceHelper.GetEscapeSequencePositions(stream.GetBuffer(), sizeof(uint), stream.Length);
            if (dataEscapePositions != null && dataEscapePositions.Count != 0)
            {// There is escape sequence in user data, recompose the stream and write it.

                // Obtain user data.
                byte[] itemData = new byte[itemDataSize];
                writer.Seek((int)itemStart, SeekOrigin.Begin);

                if (stream.Read(itemData, 0, (int)itemDataSize) != (int)itemDataSize)
                {
                    throw new Exception("Read logic failure.");
                }

                // Reset.
                writer.Seek(0, SeekOrigin.Begin);
                stream.SetLength(0);

                // Write the index first.
                writer.Write(itemIndex);

                // Default indication of no escape sequences in user data.
                writer.Write(true);

                // Placeholder for the real length value.
                writer.Write(uint.MaxValue);

                // Write the count of escape positions (int).
                writer.Write(dataEscapePositions.Count);

                foreach (long position in dataEscapePositions)
                {// Now write all the escape positions.
                    uint compesatedPosition = (uint)position + (uint)(sizeof(int) * (dataEscapePositions.Count + 1));
                    writer.Write(compesatedPosition);
                }

                // Write the user data.
                writer.Write(itemData, 0, itemData.Length);

                // Fix the user data.
                foreach (long position in dataEscapePositions)
                {
                    uint compesatedPosition = (uint)position + (uint)(sizeof(int) * (dataEscapePositions.Count + 1));
                    if (writer.Seek((int)compesatedPosition, SeekOrigin.Begin) != compesatedPosition)
                    {
                        throw new Exception("Read logic failure 2.");
                    }

                    for (int i = 0; i < sequenceHelper.EscapeSequence.Length; i++)
                    {// Fill with zeroes.
                        writer.Write(0);
                    }
                }

                // Finally - write the actual size.
                writer.Seek(sizeof(uint) + 1, SeekOrigin.Begin);
                writer.Write((int)stream.Length);
            }

            //List<long> positions = sequenceHelper.GetEscapeSequencePositions(stream.GetBuffer(), 0, stream.Length);
            //if (positions != null)
            //{
            //    int h = 2;
            //}

            return true;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="dummyCreate">Will not create the item, but load everything else.</param>
        public static bool CreateItemFromBuffer(IItemSerializer serializer, DataSeriesSequenceHelper sequenceHelper, 
                                                byte[] buffer, long bufferLength, bool dummyCreate, out uint index, out uint fullItemSize, out object item)
        {
            index = uint.MaxValue;
            fullItemSize = 0;
            item = null;

            if (serializer == null || bufferLength < ItemHeaderSize)
            {
                return false;
            }

            MemoryStream stream = new MemoryStream(buffer);
            using (BinaryReader binaryReader = new BinaryReader(stream))
            {
                index = binaryReader.ReadUInt32();
                bool hasEscapes = binaryReader.ReadBoolean();
                fullItemSize = binaryReader.ReadUInt32();


                if (hasEscapes)
                {
                    int escapesCount = binaryReader.ReadInt32();
                    long userDataReadingPosition = stream.Position + (escapesCount * sizeof(uint));

                    for (int i = 0; i < escapesCount; i++)
                    {// Restore escaped values.
                        uint position = binaryReader.ReadUInt32();
                        if (stream.Seek(position, SeekOrigin.Begin) != position)
                        {
#if Matrix_Diagnostics
                            SystemMonitor.OperationError("Escape symbols stored position error.");
#endif
                            return false;
                        }

                        stream.Write(sequenceHelper.EscapeSequence, 0, sequenceHelper.EscapeSequence.Length);
                    }

                    // Restore data reading position.
                    stream.Seek(userDataReadingPosition, SeekOrigin.Begin);
                }

                if (fullItemSize != buffer.Length)
                {
                    return false;
                }

                if (dummyCreate == false)
                {
                    if (serializer.ReadItem(binaryReader, index, out item) == false)
                    {
                        return false;
                    }
                }
            }

            return true;
        }


        /// <summary>
        /// 
        /// </summary>
        public static uint? GetStreamLastIndex(IItemSerializer serializer, string filePath)
        {
            using (DataSeriesReader reader = new DataSeriesReader())
            {
                if (reader.Initialize(serializer, filePath) == false)
                {
                    throw new OperationCanceledException("Failed to initialize reader.");
                }

                byte[] itemData = reader.RawReadLast(null);
                if (itemData == null || itemData.Length == 0)
                {
                    return null;
                }

                uint index;

                if (reader.ReadLast(0, out index) == null)
                {
                    return null;
                }

                //uint index, size;
                //object item;
                //if (CreateItemFromBuffer(serializer, itemData, itemData.Length, true, out index, out size, out item) == false)
                //{
                //    return null;
                //}

                return index;
            }
        }

    }
}
